#include <GL/gl.h>

#include <algorithm>
#include <math.h>

#include "asserts.hpp"
#include "draw_number.hpp"
#include "draw_scenario.hpp"
#include "hex_geometry.hpp"
#include "player.hpp"
#include "raster.hpp"
#include "scenario.hpp"
#include "terrain.hpp"
#include "unit.hpp"
#include "unit_type.hpp"
#include "unit_utils.hpp"
#include "wml_node.hpp"
#include "wml_utils.hpp"

namespace game {

unit::unit(wml::const_node_ptr node)
  : loc_(wml::get_int(node, "x", -1), wml::get_int(node, "y", -1)),
    hitpoints_(wml::get_int(node, "hitpoints", 10)),
    moves_left_(wml::get_int(node, "moves_left")),
    selected_at_(0), percent_along_path_(0), move_path_index_(0),
    description_(node->attr("description")),
    attack_increase_(wml::get_int(node, "attack_increase")),
    defense_increase_(wml::get_int(node, "defense_increase"))
{
}

unit::unit()
  : hitpoints_(10), moves_left_(0), selected_at_(0), percent_along_path_(0), move_path_index_(0),
    attack_increase_(0), defense_increase_(0)
{}

unit::~unit()
{}

std::string unit::description() const
{
	if(description_.empty() == false) {
		return description_;
	}
	return type().description();
}

int unit::attack() const
{
	return type().attack() + attack_increase_;
}

int unit::defense() const
{
	return type().defense() + defense_increase_;
}

int unit::aggression() const
{
	return type().aggression();
}

int unit::movement() const
{
	return type().movement();
}

int unit::attack_range() const
{
	return type().attack_range();
}

void unit::increase_attack(int amount)
{
	attack_increase_ += amount;
}

void unit::increase_defense(int amount)
{
	defense_increase_ += amount;
}

unit_type::FIGHTING_STYLE unit::fighting_style() const
{
	return type().fighting_style();
}

void unit::draw(int* pixel_x_ptr, int* pixel_y_ptr) const
{
	if(move_path_.empty() == false) {
		percent_along_path_ += 10;
		if(percent_along_path_ >= 100) {
			move_path_index_++;
			percent_along_path_ = 0;
			if(move_path_index_ >= move_path_.size() - 1) {
				move_path_.clear();
				move_path_index_ = 0;
			}
		}
	}

	int pixel_x = tile_pixel_x(loc_);
	int pixel_y = tile_pixel_y(loc_);

	if(move_path_.size() > move_path_index_+1) {
		const int x1 = tile_pixel_x(move_path_[move_path_index_]);
		const int y1 = tile_pixel_y(move_path_[move_path_index_]);
		const int x2 = tile_pixel_x(move_path_[move_path_index_+1]);
		const int y2 = tile_pixel_y(move_path_[move_path_index_+1]);

		pixel_x = (x1*(100-percent_along_path_) + x2*percent_along_path_)/100;
		pixel_y = (y1*(100-percent_along_path_) + y2*percent_along_path_)/100;
	}

	if(pixel_x_ptr) {
		*pixel_x_ptr = pixel_x;
	}

	if(pixel_y_ptr) {
		*pixel_y_ptr = pixel_y;
	}

	type().standing_animation().team_animation(unit_color_index(*this)).draw(pixel_x, pixel_y);

	if(selected_at_ == draw_cycle()) {
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		const GLfloat alpha = 0.5 + sin(draw_cycle()/10.0)*0.5;
		glColor4f(1.0,1.0,1.0,alpha);
		type().standing_animation().team_animation(unit_color_index(*this)).draw(pixel_x, pixel_y);
		glColor4f(1.0,1.0,1.0,1.0);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}

	if(get_commander().is_squad_selected()) {
		graphics::texture selection_marker;
		if(get_commander().commands_left() > 0) {
			selection_marker = graphics::texture::get("squad.png");
		} else {
			selection_marker = graphics::texture::get("squad.png", "rgba(b,g,r,a)");
		}

		graphics::blit_texture(selection_marker, pixel_x, pixel_y);
	}

	draw_number(hitpoints_, 2, get_commander().owner().color_index(), pixel_x + 40, pixel_y + 40);
}

void unit::get_hit(int damage)
{
	hitpoints_ -= damage;
	if(hitpoints_ < 0) {
		hitpoints_ = 0;
	}
}

void unit::heal(int amount)
{
	hitpoints_ += amount;
	if(hitpoints_ > 10) {
		hitpoints_ = 10;
	}
}

bool unit::destroyed() const
{
	return hitpoints_ == 0 || &get_commander() != this && get_commander().destroyed();
}

void unit::set_loc(const hex::location& loc)
{
	loc_ = loc;
}

void commander_unit::draw(int* pixel_x_ptr, int* pixel_y_ptr) const
{
	int pixel_x, pixel_y;
	unit::draw(&pixel_x, &pixel_y);
	if(pixel_x_ptr) {
		*pixel_x_ptr = pixel_x;
	}

	if(pixel_y_ptr) {
		*pixel_y_ptr = pixel_y;
	}

	for(int n = 0; n != commands_left(); ++n) {
		graphics::blit_texture(graphics::texture::get("command.png"), pixel_x, pixel_y + n*8);
	}
}

bool commander_unit::use_command()
{
	if(commands_left_ <= 0) {
		return false;
	}

	--commands_left_;
	return true;
}

std::vector<const_soldier_unit_type_ptr> commander_unit::recruits() const
{
	return type_->recruits();
}

void commander_unit::remove_dead_soldiers()
{
	foreach(soldier_unit_ptr& s, soldiers_) {
		if(s->destroyed()) {
			s = soldier_unit_ptr();
		}
	}

	soldiers_.erase(std::remove(soldiers_.begin(), soldiers_.end(), soldier_unit_ptr()), soldiers_.end());
}

void unit::new_turn()
{
	moves_left_ = movement();
}

void unit::set_selected()
{
	selected_at_ = draw_cycle();
	get_commander().set_squad_selected();
}

int unit::movement_cost(const hex::location& a, const hex::location& b) const
{
	if(loc() != a && is_ezoc(*this, a)) {
		//if we're not already at this location, and it's an ezoc, then moving
		//away from it is impossible.
		return 1000;
	}

	return move_cost(b);
}

int unit::estimated_cost(const hex::location& a, const hex::location& b) const
{
	return move_cost(b)*hex::distance_between(a, b);
}

bool unit::allowed_to_move(const hex::location& loc) const
{
	unit_ptr occupying_unit = scenario::current()->get_unit(loc);
	if(occupying_unit && units_hostile(*this, *occupying_unit)) {
		return false;
	}

	return move_cost(loc) > 0;
}

bool unit::legal_move_endpoint(const hex::location& loc) const
{
	unit_ptr occupying_unit = scenario::current()->get_unit(loc);
	if(occupying_unit && occupying_unit != this) {
		return false;
	}

	return true;
}

int unit::terrain_defense_bonus(const hex::location& loc) const
{
	scenario* scenario = scenario::current();
	if(!scenario) {
		return -1;
	}

	if(!scenario->map().is_loc_on_map(loc)) {
		return -1;
	}

	hex::const_tile_ptr tile = scenario->map().get_tile(loc);
	ASSERT_LOG(tile, "Could not find tile at " << loc.x() << "," << loc.y());
	
	return terrain_defense_bonus(tile->terrain()->base());
}

int unit::move_cost(const hex::location& loc) const
{
	scenario* scenario = scenario::current();
	if(!scenario) {
		return -1;
	}

	if(!scenario->map().is_loc_on_map(loc)) {
		return -1;
	}

	hex::const_tile_ptr tile = scenario->map().get_tile(loc);
	ASSERT_LOG(tile, "Could not find tile at " << loc.x() << "," << loc.y());
	
	return move_cost(tile->terrain()->base());
}

void unit::use_moves(int moves)
{
	moves_left_ -= moves;
}

void unit::move_along_path(const std::vector<hex::location>& path)
{
	move_path_.insert(move_path_.end(), path.begin(), path.end());
	percent_along_path_ = 0;
	move_path_index_ = 0;
	if(move_path_.size() == 1) {
		move_path_.clear();
	}
}

int unit::terrain_defense_bonus(const_base_terrain_ptr terrain) const
{
	return terrain->cover();
}

int unit::move_cost(const_base_terrain_ptr terrain) const
{
	return terrain->move_cost();
}

soldier_unit::soldier_unit(commander_unit& commander, const_soldier_unit_type_ptr type)
  : commander_(commander), type_(type)
{
}

soldier_unit::soldier_unit(commander_unit& commander, wml::const_node_ptr node)
  : unit(node), commander_(commander)
{
	const_unit_type_ptr type = unit_type::get(node->attr("type"));
	type_ = boost::static_pointer_cast<const soldier_unit_type>(type);
}

commander_unit& soldier_unit::get_commander()
{
	return commander_;
}

void soldier_unit::new_turn()
{
	unit::new_turn();

	if(hex::distance_between(loc(), get_commander().loc()) == 1) {
		//heal two hitpoints if we're next to our leader.
		heal(2);
	}
}

const commander_unit& soldier_unit::get_commander() const
{
	return commander_;
}

commander_unit::commander_unit(player& owner, wml::const_node_ptr node)
  : unit(node), owner_(&owner), id_(node->attr("id")), commands_left_(wml::get_int(node, "commands_left")), squad_selected_at_(0), portrait_(node->attr("portrait")),
    attack_bonus_increase_(wml::get_int(node, "attack_bonus_increase")),
    defense_bonus_increase_(wml::get_int(node, "defense_bonus_increase")),
    command_radius_increase_(wml::get_int(node, "command_radius_increase")),
    level_(wml::get_int(node, "level")),
    experience_(wml::get_int(node, "experience"))
{
	const_unit_type_ptr type = unit_type::get(node->attr("type"));
	type_ = boost::static_pointer_cast<const commander_unit_type>(type);

	FOREACH_WML_CHILD(soldier_node, node, "soldier") {
		const int duplicates = wml::get_int(soldier_node, "duplicates", 1);
		for(int n = 0; n < duplicates; ++n) {
			soldiers_.push_back(soldier_unit_ptr(new soldier_unit(*this, soldier_node)));
		}
	}
}

commander_unit::~commander_unit()
{
	std::cerr << "DESTROY UNIT: '" << description() << "'\n";
}

void commander_unit::set_owner(player& p)
{
	owner_ = &p;
}

void commander_unit::add_soldier(soldier_unit_ptr soldier)
{
	soldiers_.push_back(soldier);
}

void commander_unit::clear_soldiers()
{
	soldiers_.clear();
}

int commander_unit::num_commands() const
{
	return type_->num_commands();
}

int commander_unit::command_radius() const
{
	return type_->command_radius() + command_radius_increase_;
}

int commander_unit::attack_bonus() const
{
	return type_->attack_bonus() + attack_bonus_increase_;
}

int commander_unit::defense_bonus() const
{
	return type_->defense_bonus() + defense_bonus_increase_;
}

void commander_unit::new_turn()
{
	unit::new_turn();
	commands_left_ = num_commands();

	//commanders heal 2 hitpoints every turn
	heal(2);
}

void commander_unit::set_squad_selected()
{
	squad_selected_at_ = draw_cycle();
}

bool commander_unit::is_squad_selected() const
{
	return squad_selected_at_ == draw_cycle();
}

commander_unit& commander_unit::get_commander()
{
	return *this;
}

const commander_unit& commander_unit::get_commander() const
{
	return *this;
}

const std::string& unit::portrait() const
{
	return type().portrait();
}

const std::string& commander_unit::portrait() const
{
	if(!portrait_.empty()) {
		return portrait_;
	}

	return unit::portrait();
}

bool commander_unit::award_experience(int amount)
{
	experience_ += amount;
	if(experience_ >= experience_required() && experience_required() != -1) {
		const commander_unit_type::level_info& lvl = type_->levels()[level_];
		increase_attack(lvl.attack);
		increase_defense(lvl.defense);
		attack_bonus_increase_ += lvl.attack_bonus;
		defense_bonus_increase_ += lvl.defense_bonus;
		command_radius_increase_ += lvl.command_radius;
		++level_;
		return true;
	} else {
		return false;
	}
}

int commander_unit::experience_required() const
{
	if(level_ >= type_->levels().size()) {
		return -1;
	} else {
		return type_->levels()[level_].experience_required;
	}
}

variant unit::get_value(const std::string& key) const
{
	if(key == "hitpoints") {
		return variant(hitpoints());
	} else if(key == "description") {
		return variant(description());
	}
	return variant();
}

}
